﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DbGenerator.Model.units.constraint.root;
using DbGenerator.Model.units.column;
using Unity.Errors.types;
using DbGenerator.Model.units.constraint;
using DbGenerator.Model.units.constraint.types;
using DbGenerator.Model.units.database;
using DbGenerator.Utilities;

namespace DbGenerator.Model.units.table
{
    public enum EConstraintType : byte
    {
        PRIMARYKEY,
        UNIQUEKEY,
        FOREIGNKEY,
        DEFAULT,
        CHECK,
        NA
    }
    public class TTable : IEnumerable<TColumn>, IEnumerator<TColumn>, IEquatable<TTable>
    {
        #region ctors
        public TTable(TTableSign sign, string tableSchema, TDatabase baseCatalog)
        {
            _TableSign = sign;
            _Columns = new List<TColumn>();
            _BaseCatalog = baseCatalog;
            _TableSchema = tableSchema;
        }
        #endregion

        #region public methods
        public void AddColumn(TColumn c)
        {
            _Columns.Add(c);
        }
        public void AddRangeColumn(TColumn[] cArr)
        {
            _Columns.AddRange(cArr);
        }
        public TColumn GetColumnByName(string columnName)
        {
            TColumn column = null;
            int columnCount = _Columns.Count, errorCount = -1;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].ColumnInfo.ColumnName == columnName)
                {
                    column = _Columns[i];
                    errorCount++;
                }
            }
            SErrorManager.CustomError()
                .Assert(errorCount == 0
                , new TErrorInfo(GetType(), "GetColumnByName")
                , "Column Name {0} duplicates {1} times."
                , columnName, errorCount);
            return column;
        }
        public string CreateTableScript()
        {
            StringBuilder sb = new StringBuilder();
            //Use [db]:
            sb.AppendFormat("\nUSE [{0}]\n", this.BaseCatalog.DatabaseName);
            //CREATE TABLE [dbo].[L_Stoklar](
            sb.AppendFormat("\nCREATE TABLE [{0}].[{1}] ("
                , _TableSchema
                , _TableSign.TableName);

            //Loop between columns:
            int columnsCount = this.Columns.Count;
            for (int i = 0; i < columnsCount; i++)
            {
                //[ID] [int] IDENTITY(1,1) NOT NULL,
                //[Kod] [nvarchar](50) NULL,
                TColumn currentColumn = this.Columns[i];
                sb.AppendFormat("\n\t{0}, ", currentColumn.ToString());
            }
            //Now collect pk-uq constraints:
            IntegrityKeysScript(sb);
            //Remove <, > from end.
            sb.Remove(sb.Length - 2, 2);
            //Scope this sentence
            sb.Append("\n)");

            //Append "ALTER TABLE"s
            AppendDKScript(sb);
            AppendCKScript(sb);

            return sb.ToString();
        }

        public string SelectColumnScript(EConstraintType constraintType)
        {
            string script = "";
            TColumn[] cols = SelectColumn(constraintType);
            int colCount = cols.Length;
            for (int i = 0; i < colCount; i++)
            {
                script += String.Format("[{0}], ", cols[i].ColumnInfo.ColumnName);
            }
            script = script.Substring(0, script.Length - 2);
            return script;
        }

        public TColumn[] SelectColumn(EConstraintType constraintType)
        {
            List<TColumn> r = new List<TColumn>();
            int columnCount = _Columns.Count;
            switch (constraintType)
            {
                case EConstraintType.PRIMARYKEY:
                    for (int i = 0; i < columnCount; i++)
                    {
                        if (_Columns[i].PKConstraint != null)
                            r.Add(_Columns[i]);
                    }
                    break;
                case EConstraintType.UNIQUEKEY:
                    for (int i = 0; i < columnCount; i++)
                    {
                        if (_Columns[i].UQConstraint != null)
                            r.Add(_Columns[i]);
                    }
                    break;
                case EConstraintType.FOREIGNKEY:
                    for (int i = 0; i < columnCount; i++)
                    {
                        if (_Columns[i].FKConstraint != null)
                            r.Add(_Columns[i]);
                    }
                    break;
                case EConstraintType.DEFAULT:
                    for (int i = 0; i < columnCount; i++)
                    {
                        if (_Columns[i].DKConstraint != null)
                            r.Add(_Columns[i]);
                    }
                    break;
                case EConstraintType.CHECK:
                    for (int i = 0; i < columnCount; i++)
                    {
                        if (_Columns[i].CKConstraint != null)
                            r.Add(_Columns[i]);
                    }
                    break;
                case EConstraintType.NA:
                default:
                    return null;
            }

            return r.ToArray();
        }
        #endregion

        #region private methods
        public string ConstructConstraintName(EConstraintType constraintType)
        {
            string sFactor = Guid.NewGuid().ToString().Replace("-", "");
            switch (constraintType)
            {
                case EConstraintType.PRIMARYKEY:
                    return "PK_" + sFactor;
                case EConstraintType.UNIQUEKEY:
                    return "UK_" + sFactor;
                case EConstraintType.DEFAULT:
                    return "DK_" + sFactor;
                case EConstraintType.CHECK:
                    return "CK_" + sFactor;
                default:
                    SErrorManager.Errors("EnumNotSet")
                        .Assert(false
                        , new TErrorInfo(GetType(), "ConstructConstraintName")
                        , typeof(EConstraintType).Name, constraintType.ToString());
                    return "";
            }
        }
        
        #endregion

        #region Append methods
        private void IntegrityKeysScript(StringBuilder sb)
        {
            /* CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED 
            (
	            [ID] ASC
            )*/
            StringBuilder script = new StringBuilder();
            TColumn[] allPK = SelectAllPK();
            TColumn[] allUQ = SelectAllUQ();

            int pkCount = allPK.Length;
            if (pkCount > 0)
            {
                script.AppendFormat("\nCONSTRAINT [{0}] PRIMARY KEY CLUSTERED \n("
                    , ConstructConstraintName(EConstraintType.PRIMARYKEY));
                for (int i = 0; i < pkCount; i++)
                {
                    script.AppendFormat("\n\t[{0}] {1}, "
                        , allPK[i].ColumnInfo.ColumnName
                        , (allPK[i].PKConstraint.CustomAttribute.Order == EOrder.ASC)
                        ? "ASC" : "DESC");
                }
                script.Remove(script.Length - 2, 2);
                script.Append("\n), ");
            }
            /* CONSTRAINT [UQ_Table_1] UNIQUE NONCLUSTERED 
            (
	            [ID] ASC
            )*/

            int uqCount = allUQ.Length;
            if (uqCount > 0)
            {
                script.AppendFormat("\nCONSTRAINT [{0}] UNIQUE NONCLUSTERED\n("
                , ConstructConstraintName(EConstraintType.UNIQUEKEY));
                for (int i = 0; i < uqCount; i++)
                {
                    script.AppendFormat("\n\t[{0}] {1}, "
                        , allUQ[i].ColumnInfo.ColumnName
                        , (allUQ[i].UQConstraint.CustomAttribute.Order == EOrder.ASC)
                        ? "ASC" : "DESC");
                }
                script.Remove(script.Length - 2, 2);
                script.Append("\n), ");
            }
            sb.Append(script);
        }

        private TColumn[] SelectAllPK()
        {
            List<TColumn> all = new List<TColumn>();
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].PKConstraint != null)
                    all.Add(_Columns[i]);
            }

            return all.ToArray();
        }

        private TColumn[] SelectAllUQ()
        {
            List<TColumn> all = new List<TColumn>();
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].UQConstraint != null)
                    all.Add(_Columns[i]);
            }

            return all.ToArray();
        }

        private void AppendCKScript(StringBuilder sb)
        {
            /*ALTER TABLE [dbo].[Table_1]  WITH CHECK ADD  CONSTRAINT [CK_Table_1_Age] CHECK  (([Age]>(10) AND [age]<(120)))
*/
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].CKConstraint != null)
                {
                    sb.AppendFormat("\nALTER TABLE [{0}].[{1}] WITH CHECK ADD  CONSTRAINT [{2}] CHECK ({3})"
                         , _TableSchema
                         , _TableSign.TableName
                         , ConstructConstraintName(EConstraintType.CHECK)
                         , _Columns[i].CKConstraint.Expression);
                }
            }
        }
        private void AppendDKScript(StringBuilder sb)
        {
            /*ALTER TABLE [dbo].[Table_1] ADD  CONSTRAINT [DF_Table_1_LastLoginDate]  DEFAULT (getdate()) FOR [LastLoginDate]

*/
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].DKConstraint != null)
                {
                    sb.AppendFormat("\nALTER TABLE [{0}].[{1}] ADD  CONSTRAINT [{2}] DEFAULT ({3}) FOR [{4}]"
                         , _TableSchema
                         , _TableSign.TableName
                         , ConstructConstraintName(EConstraintType.DEFAULT)
                         , (SDbTypeUtilities.IsText(_Columns[i].ColumnInfo.DataType))
                             ? "'" + _Columns[i].DKConstraint.DefaultValue + "'"
                             : _Columns[i].DKConstraint.DefaultValue
                         , _Columns[i].ColumnInfo.ColumnName);
                }
            }
        }
        private TDKConstraint[] CollectDKs()
        {
            List<TDKConstraint> r = new List<TDKConstraint>();
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].FKConstraint != null)
                    r.Add(_Columns[i].DKConstraint);
            }
            if (r.Count != 0)
                return r.ToArray();
            return null;
        }
        private TCKConstraint[] CollectCKs()
        {
            List<TCKConstraint> r = new List<TCKConstraint>();
            int columnCount = _Columns.Count;
            for (int i = 0; i < columnCount; i++)
            {
                if (_Columns[i].FKConstraint != null)
                    r.Add(_Columns[i].CKConstraint);
            }
            if (r.Count != 0)
                return r.ToArray();
            return null;
        }
        #endregion

        #region overriden methods
        public override string ToString()
        {
            return _TableSign.TableName;
        }
        #endregion

        #region properties
        private string _TableSchema;
        public string TableSchema
        {
            get { return _TableSchema; }
        }
        private TDatabase _BaseCatalog;
        public TDatabase BaseCatalog
        {
            get { return _BaseCatalog; }
            set { _BaseCatalog = value; }
        }

        private TTableSign _TableSign;
        public TTableSign TableSign
        {
            get { return _TableSign; }
            set { _TableSign = value; }
        }

        private List<TColumn> _Columns;
        public List<TColumn> Columns
        {
            get { return _Columns; }
            protected set { _Columns = value; }
        }
        #endregion

        #region private fields
        private int _Position;
        #endregion

        #region IEnumerable<TColumn> Members

        public IEnumerator<TColumn> GetEnumerator()
        {
            return (IEnumerator<TColumn>)this;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _Columns.GetEnumerator();
        }

        #endregion

        #region IEnumerator<TColumn> Members

        public TColumn Current
        {
            get { return _Columns[_Position]; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this._Columns = null;
            GC.Collect();
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            return ++_Position < _Columns.Count;
        }

        public void Reset()
        {
            _Position = -1;
        }

        #endregion

        #region IEquatable<TTable> Members

        public bool Equals(TTable other)
        {
            return _BaseCatalog.DatabaseName == other._BaseCatalog.DatabaseName
                && _TableSign.TableName == _TableSign.TableName
                && _TableSchema == other.TableSchema;
        }

        #endregion
    }
}
